'''GBA火焰之纹章烈火之剑(简称FE07)的动态修改,目的是让成长数值期望化(四舍五入)'''
 
import csv
import ctypes
import sys

class FE07:

    def __init__(self,pid):
        # 经研究得出的常量
        self.MAXMEMBERS=44  # 最大修改人数
        self.BASEADDR=0x05A8F50  # 基址,该地址的值是数据区的首地址,测试找出来的数值.对VBA1.80beta版本似乎有效
        self.BASEDELTA=0x2bd48  # 数据区首地址偏移该数值后,就是首人物的数据首地址了
        self.ADDRDELTA=72  # 人物数据之间的字节数间隔
        self.IDADDR=4   # 人物肖像id地址相对应首地址的偏移
        self.JOBADDR=8  # 人物职业id地址相对应首地址的偏移
        self.LVADDR=12  # 等级地址
        self.MHPADDR=22  # 最大HP地址
        self.PARAMADDR=24 # 6个参数首地址,每一字节依次为:力技速守魔运
        self.MEMRWDLL=ctypes.CDLL(sys.path[0]+'\\'+r'MemReadWriteDLL.dll')  # dll读写内存相关模块,自制的dll
        self.isOk=False # 是否成功打开进程读取数据找到首地址等,最后变为True.若中途失败则保持False
        # 打开进程,不成功则中断,但hProcess会保留,可用于访问进程是否打开成功
        self.hProcess=self.MEMRWDLL.GetHandleFromPid((int(pid)))
        if (self.hProcess<1):
            print('无法打开进程')
            return
        else:
            print('成功打开进程')
        '''
        # 查找数据区首地址,特点是首地址+0x1c读取的32位数,恰好等于首地址.似乎对gba模拟器有效?
        for addr in range(0x00000000,0xf0000000,0x10000):
            v=int(self.MEMRWDLL.ReadDWORD(self.hProcess,addr+0x1c))
            if v==addr:
                # 满足条件可找到数据区首地址,但人物数据首地址偏移不固定,只好慢慢找了.这里很易出问题啊
                successFlag=False
                for d in range(0,80000,8):
                    addr2=v+300000+d
                    if self.judgeRoleAddr(addr2):
                        successFlag=True
                        break
                if successFlag:
                    print('成功找到数据区首地址:',hex(v))
                    print('人物数据首地址:',hex(addr2))
                    addr3=int(self.MEMRWDLL.ReadDWORD(self.hProcess,0x05A8F50))
                    print('通过地址偏移找到的人物数据首地址:',hex(addr3+0x2bd48))
                    break
            if addr>0xef000000:
                print('找不到数据区首地址,中止')
                return
        '''
        # 上面的一大堆保留,是旧的找首地址方法(好糟糕的方法),避免找首地址失败.

        # 进程打开完毕,可通过访问hProcess知道是否成功
        self.roles={}  # 角色表
        self.highjobs={}  # 高级职业表
        # self.memAddr=addr2 # 人物数据首地址
        self.memAddr=int(self.MEMRWDLL.ReadDWORD(self.hProcess,self.BASEADDR))
        self.memAddr+=self.BASEDELTA
        print('First role address:',hex(self.memAddr))
        self.readData(shown=False)  # 从文件中读取角色表与高级职业表
        self.isOk=True
        return

    # 从文件中读取角色表与高级职业表
    def readData(self,shown=True):
        # 高级职业表,key是职业的数字id,value是一个表.name是名字,params是增加参数列表,limit是上限列表
        with open('highJobs.csv','r') as f:
            jobList=list(csv.reader(f))
        for i in range(1,len(jobList)):
            jobId=eval('0x'+jobList[i][1])
            val={}
            val['name']=jobList[i][0]
            val['params']=[int(jobList[i][j]) for j in range(2,9)]
            val['limit']=[int(jobList[i][j]) for j in range(9,16)]
            self.highjobs[jobId]=val
        # 人物表,key是人物的数字id(肖像代码),value还是一个表,包含name,job,startLv,jobChangeLv,params(参数表),rates(成长率表)几个项目
        with open('roles.csv','r') as f:
            roleList=list(csv.reader(f))
        for i in range(1,len(roleList)-1,2):
            roleId=eval('0x'+roleList[i+1][0])
        # 尚未确定的人物,其id全部为0
            if(roleId<1):
                continue
            newRole={}
            newRole['name']=roleList[i][0]
            newRole['job']=eval('0x'+roleList[i+1][1])
            newRole['startLv']=int(roleList[i][2])
            newRole['jobChangeLv']=int(roleList[i][10])
            newRole['params']=[int(roleList[i][j]) for j in range(3,10)]
            newRole['rates']=[int(roleList[i+1][j]) for j in range(3,10)]
            self.roles[roleId]=newRole
        if shown:
            print('highjobs(%d):' % len(self.highjobs))
            for jobId in self.highjobs:
                print(hex(jobId),self.highjobs[jobId]['name'])
                print(self.highjobs[jobId]['params'])
                print(self.highjobs[jobId]['limit'])
            print('\nroles(%d):' % len(self.roles))
            for roleId in self.roles:
                print(hex(roleId),self.roles[roleId])
        return

    # 给出角色id,当前等级,职业id,计算能力期望值,返回一个参数列表
    def computeRole(self,roleId,lv,jobId,shown=False):
        role=self.roles[roleId]
        result=role['params'][:]
        rate=role['rates']
        jobChanged=False # 是否转职过
        # 判断是否转职过
        if jobId==role['job']:
            jobChanged=False
        else:
            jobChanged=True
        if jobChanged:
            limit=[60,20,20,20,20,20,30]
            jobChangeLv=role['jobChangeLv']
            for i in range(0,7):
                v=result[i]+rate[i]*(jobChangeLv-role['startLv'])/100
                if v>limit[i]:
                    v=limit[i]
                result[i]=v
            limit=self.highjobs[jobId]['limit']
            for i in range(0,7):
                v=round(result[i]+rate[i]*(lv-1)/100+self.highjobs[jobId]['params'][i])
                if v>limit[i]:
                    v=limit[i]
                result[i]=v
        else:
            limit=[60,20,20,20,20,20,30]
            if jobId in self.highjobs:
                limit=self.highjobs[jobId]['limit']
            for i in range(0,7):
                v=round(result[i]+rate[i]*(lv-role['startLv'])/100)
                if v>limit[i]:
                    v=limit[i]
                result[i]=v
        if shown:
            print(result)
        return result

    # 读取内存中第index个(index由0开始)角色的资料,返回一个表:roleId,lv,job,params(列表)
    def readMemRole(self,index,shown=True):
        result={}
        addr=self.memAddr+self.ADDRDELTA*index # 需要读取的人物的数据首地址
        result['roleId']=int(self.MEMRWDLL.ReadDWORD(self.hProcess,addr+self.IDADDR))
        result['lv']=int(self.MEMRWDLL.ReadBYTE(self.hProcess,addr+self.LVADDR))
        result['job']=int(self.MEMRWDLL.ReadDWORD(self.hProcess,addr+self.JOBADDR))
        params=[]
        params.append(int(self.MEMRWDLL.ReadBYTE(self.hProcess,addr+self.MHPADDR)))
        for i in range(0,6):
            params.append(int(self.MEMRWDLL.ReadBYTE(self.hProcess,addr+self.PARAMADDR+i)))
        result['params']=params
        if shown:
            print('roleId:',hex(result['roleId']),self.roles[result['roleId']]['name'])
            jobName='NotHighJob'
            if result['job'] in self.highjobs:
                jobName=self.highjobs[result['job']]['name']
            print('job:',hex(result['job']),jobName)
            print('lv:',result['lv'])
            print('params:',params)
        return result

    # 向内存的第index个角色写入一组参数(MHP,力,技,速,守,魔,幸),成功返回true
    def writeMemRole(self,index,params):
        addr=self.memAddr+self.ADDRDELTA*index # 需要写入的人物的数据首地址
        # 由于人物的id的4个字节,后2个总是c4,08,可检测一下,如果不对则放弃写入
        test1=int(self.MEMRWDLL.ReadBYTE(self.hProcess,addr+self.IDADDR+2))
        if test1!=0xc4:
            return False
        test2=int(self.MEMRWDLL.ReadBYTE(self.hProcess,addr+self.IDADDR+3))
        if test2!=0x08:
            return False
        # 检查结束,开始写入        
        self.MEMRWDLL.WriteBYTE(self.hProcess,addr+self.MHPADDR,ctypes.c_byte(params[0]))
        for i in range(0,6):
            self.MEMRWDLL.WriteBYTE(self.hProcess,addr+self.PARAMADDR+i,ctypes.c_byte(params[i+1]))
        return True

    # 检测一个地址是否可能是人物数据首地址,没啥好办法,只好检测一下相同间隔的特定位置是否固定数值了,因为角色的肖像ID高两位总是c4,08
    def judgeRoleAddr(self,addr):
        if addr==0x021fb2c8:
            print('aaaa')
        for i in range(0,5):
            test1=int(self.MEMRWDLL.ReadBYTE(self.hProcess,addr+6+self.ADDRDELTA*i))
            test2=int(self.MEMRWDLL.ReadBYTE(self.hProcess,addr+7+self.ADDRDELTA*i))
            if (test1!=0xc4 or test2!=0x08):
                return False
        return True

    # 主函数,把所有人物的数据改为期望值.返回成功修改的人数
    def main(self,shown=True):
        result=0
        for i in range(0,self.MAXMEMBERS):
            role=self.readMemRole(i,shown=False)
            if not role['roleId'] in self.roles:
                continue
            params=self.computeRole(role['roleId'],role['lv'],role['job'],shown=False)
            written=self.writeMemRole(i,params)
            if written:
                result+=1
                if shown:
                    print('role written:',self.roles[role['roleId']]['name'],'id:',hex(role['roleId']))
        return result


# 开始
if __name__=='__main__':
    print('烈火之剑数值期望化脚本,由setycyas制作,版本0.01,完成与2017年6月21日')
    pid=input('Input your pid:\n')
    fe07=FE07(pid)
    while(fe07.isOk):
        text=input('Press Enter to rewrite all data,press "exit"+Enter to exit\n')
        if text=='exit':
            break
        print('Data written: %d' % fe07.main())
    